﻿using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;

namespace Borg.Framework
{
    [Extension, Plug("Borg.LayoutManager")]
    public class LayoutManager : ExtensionBase, ILayoutManager1
    {
        internal static readonly Dictionary<PlugInfo, LayoutInfo> layoutInfos =
            new Dictionary<PlugInfo, LayoutInfo>();

        private readonly List<ILayoutPanel> _layoutPanels;
        private SlotInfo _layoutSlot;
        private PlugTypeInfo _panelPlugType;

        private bool _wasLayoutSlotAutoPlug;
        private bool _wasLayoutSlotAutoRelease;
        private bool _wasLayoutSlotOpen;

        public LayoutManager()
        {
            _layoutPanels = new List<ILayoutPanel>();
        }

        public LayoutManager(SlotInfo layoutSlot)
            : this(layoutSlot, string.Empty, string.Empty)
        { }

        public LayoutManager(SlotInfo layoutSlot, string layoutPanelTag, string elementTag)
        {
            _layoutPanels = new List<ILayoutPanel>();
            
            LayoutPanelTag = layoutPanelTag;
            ElementTag = elementTag;
            LayoutSlot = layoutSlot;
        }

        private void Activate()
        {
            _layoutSlot.Registered += RegisterElement;
            _layoutSlot.Deregistered += DeregisterElement;

            if (!_layoutSlot.IsOpen)
            {
                _layoutSlot.Open();
                _layoutSlot.Register(_panelPlugType);
            }
            else
                foreach (var pti in _layoutSlot.RegisteredPlugTypeInfos)
                    RegisterElement(pti);
        }

        public static void AddLayoutInfo(PlugInfo plug, LayoutInfo layoutInfo)
        {
            layoutInfos[plug] = layoutInfo;
        }

        public ExtensionInfo AddPanel(PanelInfo panelInfo, LayoutInfo layoutInfo)
        {
            var item = (ILayoutPanel)_panelPlugType.ExtensionTypeInfo.CreateUniqueExtension().Object;

            item.PanelInfo = panelInfo;
            item.LayoutInfo = layoutInfo;

            _layoutPanels.Add(item);
            layoutInfos.Add(item.ElementPlug, layoutInfo);

            item.ElementSlot.Plugged += UpdateLayoutInfo;
            item.ExtensionInfo.Released += RemoveLayoutPanel;

            PlugLayoutPanel(item);

            return item.ExtensionInfo;
        }

        private void Deactivate()
        {
            _layoutSlot.Unplug();

            foreach (var panel in _layoutPanels.ToArray())
                RemoveLayoutPanel(panel);

            _layoutSlot.Registered -= RegisterElement;
            _layoutSlot.Deregistered -= DeregisterElement;
        }

        private static void DeregisterElement(PlugTypeInfo plugType)
        { }

        private static void DeregisterElement(object sender, RegisterEventArgs args)
        {
            DeregisterElement(args.PlugTypeInfo);
        }

        private ILayoutPanel FindHostPanel(ILayoutPanel layoutPanel)
        {
            ILayoutPanel panel = null;

            foreach (var layout in _layoutPanels)
                if (layoutPanel.LayoutInfo.NestingLevel > layout.LayoutInfo.NestingLevel &&
                    (panel == null || panel.LayoutInfo.NestingLevel < layout.LayoutInfo.NestingLevel))
                    panel = layout;

            return panel;
        }

        private PlugTypeInfo FindPanelPlugType(IEnumerable<PlugTypeInfo> plugTypes)
        {
            foreach (var pti in
                from plugType in plugTypes
                where _layoutSlot.Name == plugType.Name
                where plugType.ExtensionTypeInfo.Type.GetInterface(typeof(ILayoutPanel).FullName) != null
                select plugType)
            {
                if (string.IsNullOrEmpty(LayoutPanelTag))
                    return pti;

                var paramValue = pti.GetParamValue("Tag") as string;

                if (!string.IsNullOrEmpty(paramValue) && LayoutPanelTag.Equals(paramValue))
                    return pti;
            }

            return null;
        }

        public static LayoutInfo GetLayoutInfo(PlugInfo plug)
        {
            return layoutInfos.ContainsKey(plug)
                ? layoutInfos[plug]
                : null;
        }

        private ILayoutPanel GetPanel(LayoutInfo layoutInfo)
        {
            foreach (var panel in _layoutPanels.
                Where(panel => panel.PanelInfo.Name == layoutInfo.Panel))
                return panel;

            foreach (var panel in _layoutPanels.
                Where(panel => panel.LayoutInfo.NestingLevel == layoutInfo.NestingLevel))
                return panel;

            return (ILayoutPanel) AddPanel(
                new PanelInfo(layoutInfo.Panel),
                new LayoutInfo(
                    layoutInfo.NestingLevel,
                    0.5,
                    SizeMode.Percent,
                    1.0)).Object;
        }

        private static bool IsExtensionNestedPanel(ILayoutPanel layoutPanel, ExtensionInfo extension)
        {
            return extension.Object is ILayoutPanel &&
                ((ILayoutPanel)extension.Object).
                LayoutInfo.NestingLevel > layoutPanel.LayoutInfo.NestingLevel;
        }

        private static void LogParseError(LogEventHandler logHandler,
            RepositoryElement plugType, string message, params string[] args)
        {
            if (logHandler == null)
                return;

            var errMsg = string.Format(
                "Error on parsing layout parameter for plug type {0}:{1}\n{2}",
                plugType.Id, plugType.Name, string.Format(message, args));

            logHandler(null, new LogEventArgs(MessageType.Error, errMsg));
        }

        private void OnLogEvent(object sender, LogEventArgs args)
        {
            Extension.LogEvent(args);
        }

        public static LayoutInfo ParseLayoutInfo(PlugTypeInfo plugType, LogEventHandler logHandler)
        {
            var paramVal = plugType.GetParamValue<string>("Layout");

            if (string.IsNullOrEmpty(paramVal))
                return null;

            var panel = string.Empty;

            var nestingLvl = 0.0;
            var order = 0.0;
            var size = 0.0;

            SizeMode sizeMode = 0;
            var args = paramVal;

            var sep = new[] { ';' };

            foreach (var arg in args.Split(sep))
            {
                var val = arg.Split(new char[1] { '=' });

                if (val.Length != 2)
                    LogParseError(
                        logHandler,
                        plugType,
                        "Invalid layout description: {0}",
                        new[] {arg});
                else
                {
                    var param = val[0].Trim();
                    var s = val[1].Trim();

                    switch (param)
                    {
                        case "Panel":
                            panel = s;
                            break;
                        
                        case "NestingLevel":
                            if (!double.TryParse(
                                s, NumberStyles.Float, CultureInfo.InvariantCulture, out nestingLvl))
                                LogParseError(logHandler, plugType,
                                    "Invalid nesting level: {0}", new[] {s});
                            break;
                        
                        case "Order":
                            if (!double.TryParse(
                                s, NumberStyles.Float, CultureInfo.InvariantCulture, out order))
                                LogParseError(logHandler, plugType,
                                    "Invalid order: {0}", new[] {s});
                            break;
                        
                        case "SizeMode":
                            bool defined;
                            try
                            {
                                sizeMode = (SizeMode) Enum.Parse(typeof (SizeMode), s);
                                defined = !Enum.IsDefined(typeof (SizeMode), sizeMode);
                            }
                            catch
                            {
                                defined = true;
                            }

                            if (defined)
                            {
                                sizeMode = 0;

                                LogParseError(logHandler, plugType,
                                    "Invalid size mode: {0}", new[] { s });
                            }
                            break;
                        
                        case "Size":
                            if (!double.TryParse(
                                s, NumberStyles.Float, CultureInfo.InvariantCulture, out size))
                                LogParseError(logHandler, plugType,
                                              "Invalid size: {0}", new[] {s});
                            break;
                    }
                }
            }

            return new LayoutInfo(panel, nestingLvl, order, sizeMode, size);
        }

        private void PlugLayoutPanel(ILayoutPanel panel)
        {
            var layout = FindHostPanel(panel);
            var elementSlot = panel.ElementSlot;

            var si =
                layout == null
                ? _layoutSlot
                : layout.ElementSlot;

            elementSlot.Open();
            elementSlot.Register(panel.ElementPlug.PlugTypeInfo);

            foreach (var pi in si.PluggedPlugInfos.
                Where(pi => IsExtensionNestedPanel(panel, pi.ExtensionInfo)))
            {
                si.Unplug(pi);
                elementSlot.Plug(pi);
            }

            si.Plug(panel.ElementPlug);
        }

        private void RegisterElement(PlugTypeInfo plugType)
        {
            if (_panelPlugType == null ||
                !string.IsNullOrEmpty(ElementTag) &&
                plugType.GetParamValue<string>("Tag") != ElementTag)
                    return;

            var layoutInfo = ParseLayoutInfo(plugType, OnLogEvent);
            var panel = GetPanel(layoutInfo);
            var ei = plugType.ExtensionTypeInfo.CreateUniqueExtension();
            
            ei.Released += RemoveLayoutInfo;
            AddLayoutInfo(ei.PlugInfos[plugType.Name], layoutInfo);

            var elementSlot = panel.ElementSlot;

            elementSlot.Register(plugType);
            elementSlot.Plug(ei.PlugInfos[plugType.Name]);
        }

        private void RegisterElement(object sender, RegisterEventArgs args)
        {
            RegisterElement(args.PlugTypeInfo);
        }

        public static void RemoveLayoutInfo(PlugInfo plug)
        {
            layoutInfos.Remove(plug);
        }

        public void RemoveLayoutInfo(object sender, ExtensionEventArgs args)
        {
            RemoveLayoutInfo(args.ExtensionInfo.PlugInfos[_layoutSlot.Name]);
        }

        private void RemoveLayoutPanel(ILayoutPanel layoutPanel)
        {
            layoutPanel.ElementSlot.Plugged -= UpdateLayoutInfo;
            layoutPanel.ExtensionInfo.Released -= RemoveLayoutPanel;

            foreach (var pi in layoutPanel.ElementSlot.PluggedPlugInfos)
            {
                pi.ExtensionInfo.Released -= RemoveLayoutInfo;
                RemoveLayoutInfo(pi);
            }

            _layoutPanels.Remove(layoutPanel);
            layoutInfos.Remove(layoutPanel.ElementPlug);
        }

        private void RemoveLayoutPanel(object sender, ExtensionEventArgs args)
        {
            RemoveLayoutPanel((ILayoutPanel)args.Extension);
        }

        private void RepositoryPluginAdded(object sender, PluginEventArgs args)
        {
            PanelPlugType =
                FindPanelPlugType(
                    from extensionType in args.ExtensionTypeInfos
                    from plugType in extensionType.PlugTypeInfos
                    select plugType);
        }

        private static void UpdateLayoutInfo(object sender, PlugEventArgs args)
        { }

        public string ElementTag { get; set; }

        public string LayoutPanelTag { get; set; }

        public SlotInfo LayoutSlot
        {
            get { return _layoutSlot; }
            set
            {
                if (_layoutSlot == value)
                    return;

                if (_layoutSlot != null)
                {
                    Deactivate();

                    _layoutSlot.AutoPlug = _wasLayoutSlotAutoPlug;
                    _layoutSlot.AutoRelease = _wasLayoutSlotAutoRelease;

                    if (!_wasLayoutSlotOpen)
                        _layoutSlot.Close();
                }

                _layoutSlot = value;

                if (_layoutSlot == null)
                    PanelPlugType = null;
                else
                {
                    _wasLayoutSlotAutoPlug = _layoutSlot.AutoPlug;
                    _wasLayoutSlotAutoRelease = _layoutSlot.AutoRelease;
                    _wasLayoutSlotOpen = _layoutSlot.IsOpen;
                    _layoutSlot.AutoPlug = false;

                    if (PanelPlugType == null || PanelPlugType.Name != _layoutSlot.Name)
                        PanelPlugType =
                            FindPanelPlugType(Runtime.Repository.PlugTypeInfos.Find(_layoutSlot.Name));
                    else
                        Activate();
                }
            }
        }

        private PlugTypeInfo PanelPlugType
        {
            get { return _panelPlugType; }
            set
            {
                if (_panelPlugType == value)
                    return;

                if (_layoutSlot != null)
                {
                    if (_panelPlugType == null)
                        Runtime.Repository.PluginAdded -= RepositoryPluginAdded;
                    else
                        Deactivate();
                }

                _panelPlugType = value;

                if (_layoutSlot == null)
                    return;

                if (_panelPlugType == null)
                    Runtime.Repository.PluginAdded += RepositoryPluginAdded;
                else
                    Activate();
            }
        }
    }
}

